﻿using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using JetBrains.DocumentModel;
using JetBrains.ProjectModel;
using JetBrains.ReSharper.Feature.Services.Bulbs;
using JetBrains.ReSharper.Feature.Services.LiveTemplates.Hotspots;
using JetBrains.ReSharper.Feature.Services.LiveTemplates.LiveTemplates;
using JetBrains.ReSharper.Intentions.Extensibility;
using JetBrains.ReSharper.Intentions.Extensibility.Menu;
using JetBrains.ReSharper.Intentions.Util;
using JetBrains.ReSharper.LiveTemplates;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.CSharp;
using JetBrains.ReSharper.Psi.CSharp.Tree;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.TextControl;
using JetBrains.Util;
using MugenMvvmToolkit.ReSharper.Highlighting;
using MugenMvvmToolkit.ReSharper.Infrastructure;
using MugenMvvmToolkit.ReSharper.PluginModels;

namespace MugenMvvmToolkit.ReSharper.ViewModels.QuickFixes
{
    [QuickFix]
    public class TaskBusyHandlerFix : IQuickFix
    {
        #region Fields

        private const string LoadingMessage = "Loading...";
        private const string MarkerMessage = "_```````___```````_";
        private readonly TaskBusyHandlerHighlighting _highlighting;

        #endregion

        #region Constructors

        public TaskBusyHandlerFix([NotNull] TaskBusyHandlerHighlighting highlighting)
        {
            if (highlighting == null) throw new ArgumentNullException("highlighting");
            _highlighting = highlighting;
        }

        #endregion

        #region Implementation of IQuickFix

        public IEnumerable<IntentionAction> CreateBulbItems()
        {
            TypeProvider typeProvider = TypeProvider
                .Create(_highlighting.Expression.PsiModule, _highlighting.Expression.GetResolveContext());
            IEnumerable<IBulbAction> actions = typeProvider
                .GetTaskBusyHandlers()
                .GroupBy(model => model.Name)
                .SelectMany(ToBulbActions);
            var bulbActions = new List<IBulbAction>(actions);
            var typeElement = typeProvider.SuppressTaskBusyHandlerAttribute.GetTypeElement();
            if (typeProvider.SuppressTaskBusyHandlerAttribute.IsResolved && typeElement != null)
            {
                _highlighting.Expressions.ForEach(expression => AddSuppressTaskAction(expression, typeElement, bulbActions));
                var rootReference = _highlighting.RootExpression.InvokedExpression as IReferenceExpression;
                var element = rootReference.GetQualifierExpressionDeclaredElement<IAttributesOwner>();
                if (element != null)
                {
                    var declaration = element
                         .GetDeclarations()
                         .OfType<IAttributesOwnerDeclaration>()
                         .FirstOrDefault();
                    var text = string.Format(UiResources.AddSuppressTaskBusyHandlerAttributeToFormat, element.ShortName);
                    bulbActions.Add(new DelegateBulbAction((solution, indicator) => ExecuteAddAttribute(declaration, typeElement), text));
                }
            }
            return bulbActions.ToQuickFixAction();
        }

        /// <summary>
        ///     Check if this action is available at the constructed context.
        ///     Actions could store precalculated info in <paramref name="cache" /> to share it between different actions
        /// </summary>
        /// <returns>
        ///     true if this bulb action is available, false otherwise.
        /// </returns>
        public bool IsAvailable(IUserDataHolder cache)
        {
            return _highlighting.IsValid();
        }

        #endregion

        #region Methods

        private static void AddSuppressTaskAction(IInvocationExpression exp, ITypeElement typeElement, IList<IBulbAction> actions)
        {
            var method = exp.Reference.GetDeclaredElement<IMethod>();
            if (method == null)
                return;
            var methodDeclaration = method
                .GetDeclarations()
                .OfType<IMethodDeclaration>()
                .FirstOrDefault();
            if (methodDeclaration == null)
                return;
            var text = string.Format(UiResources.AddSuppressTaskBusyHandlerAttributeToFormat, method.ShortName);
            actions.Add(new DelegateBulbAction((solution, indicator) => ExecuteAddAttribute(methodDeclaration, typeElement), text));
        }

        private IEnumerable<IBulbAction> ToBulbActions(IEnumerable<TaskBusyHandlerMetaModel> models)
        {
            IInvocationExpression expression = _highlighting.Expression;
            if (expression.Reference == null)
                return Enumerable.Empty<IBulbAction>();
            var resolve = expression.Reference.Resolve().DeclaredElement as IParametersOwner;
            if (resolve == null)
                return Enumerable.Empty<IBulbAction>();
            IType returnType = resolve.ReturnType;
            if (!TypeExtensions.TryResolve(ref returnType))
                return Enumerable.Empty<IBulbAction>();
            TaskBusyHandlerMetaModel handler = models.FirstOrDefault(model => model.IsSupported(returnType, resolve.ShortName));
            if (!handler.IsValid)
                return Enumerable.Empty<IBulbAction>();
            var result = new List<IBulbAction>();
            if (handler.MessageParameter == null || handler.MessageParameter.IsOptional)
                result.Add(new DelegateBulbAction((solution, indicator) => Execute(solution, handler, false),
                    string.Format(UiResources.AddBusyHandlerFormat, handler.Name)));
            if (handler.IsMessageObjectOrString)
                result.Add(new DelegateBulbAction((solution, indicator) => Execute(solution, handler, true),
                    string.Format(UiResources.AddBusyHandlerWithMessageFormat, handler.Name)));
            return result;
        }

        private Action<ITextControl> Execute(ISolution solution, TaskBusyHandlerMetaModel model, bool withMessage)
        {
            IInvocationExpression invocationExpression = _highlighting.Expression;
            CSharpElementFactory elementFactory = CSharpElementFactory.GetInstance(invocationExpression);
            string format = withMessage ? "$0.{0}(this, \"" + MarkerMessage + "\")" : "$0.{0}(this)";
            ICSharpExpression expression = elementFactory.CreateExpression(
                string.Format(format, model.Method.ShortName), invocationExpression);
            Extensions.AddTypeNamespace(elementFactory, TypeFactory.CreateType(model.Class), invocationExpression);
            expression = invocationExpression.ReplaceBy(expression);
            if (!withMessage)
                return null;
            var literalExpression = expression
                .FindChilds<ICSharpLiteralExpression>()
                .FirstOrDefault(sharpLiteralExpression => Equals(sharpLiteralExpression.ConstantValue.Value, MarkerMessage));
            if (literalExpression == null)
                return null;

            return control =>
            {
                DocumentRange range = literalExpression.Literal.GetDocumentRange();
                range = new DocumentRange(range.Document,
                    new TextRange(range.TextRange.StartOffset + 1, range.TextRange.EndOffset - 1));
                var field = new TemplateField("NAME", new TextHotspotExpression(new[] { LoadingMessage }), 0);
                LiveTemplatesManager.Instance.TryExecuteHotspotDefault(solution, control, new HotspotInfo(field, range));
            };
        }

        private static void ExecuteAddAttribute(IAttributesOwnerDeclaration declaration, ITypeElement attributeType)
        {
            CSharpElementFactory elementFactory = CSharpElementFactory.GetInstance(declaration);
            var attribute = elementFactory.CreateAttribute(attributeType);
            attribute = declaration.AddAttributeAfter(attribute, null);
            ContextActionUtils.FormatWithDefaultProfile(attribute);
        }

        #endregion
    }
}